#include "stdafx.h"
#include "LitScriptPreprocessor.h"
#include "LitScriptEngine.h"

namespace Lit
{
	ScriptPreprocessor::Section::Section( const Section& rk )
		: d(rk.d), name(rk.name), line_offset(rk.line_offset)
	{}
	ScriptPreprocessor::Section::Section( const std::string &nam, const std::string &str, const size_t line_offset_ )
	{ 
		line_offset = line_offset_;
		name = nam;
		Ogre::StringVector v = Ogre::StringUtil::split(str, ScriptPreprocessor::Win32Endl );
		for( auto i=v.begin(); i!=v.end(); i++ ) d.push_back( (*i) );
	}
	std::string ScriptPreprocessor::Section::join( const std::string &link ) const
	{
		std::string ret;
		for( auto i=d.begin(); i!=d.end();i++ )
			ret += (*i) + link;
		return ret;
	}
	size_t ScriptPreprocessor::Section::numLines( ) const
	{
		return d.size();
	}
		
	ScriptPreprocessor::ScriptPreprocessor(){
		mInclude = boost::bind( ScriptPreprocessor::_dummy_include, _1 );
		mDefine = boost::bind( ScriptPreprocessor::_dummy_define, _1, _2 );
	}
	ScriptPreprocessor::~ScriptPreprocessor(){}

	bool ScriptPreprocessor::_dummy_include( const FilePath& path )
	{
#ifndef NDEBUG
		ScriptEngine::getLog()->logMessage("Scanning includes for "+path.str(), Ogre::LML_NORMAL, true);
#endif
		return true;
	}
	void ScriptPreprocessor::_dummy_define( const std::string&name, const std::string &value )
	{
#ifndef NDEBUG
		ScriptEngine::getLog()->logMessage("Defining preprocessor variable "+name+":"+value, Ogre::LML_NORMAL, true );
#endif
	}

	void ScriptPreprocessor::setIncludeCallback( IncludeCallbackType cbk ){ mInclude = cbk; }
	void ScriptPreprocessor::setDefineCallback( DefineCallbackType cbk ){ mDefine = cbk; }
	
	const std::string ScriptPreprocessor::Win32Endl = "\r\n";
	const std::string ScriptPreprocessor::UnixEndl = "\n";
	const std::string ScriptPreprocessor::OSXEndl = "\n";

	void ScriptPreprocessor::processDirective( const std::string &unit_name, const std::string &line, size_t line_id )
	{
		if( line.empty() )
			return;
		if( Ogre::StringUtil::startsWith( line, "#include", false ) )
		{
			size_t p1 = line.find_first_of("\"",8);
			size_t p2 = line.find_last_of("\"",p1);
			if( p1 == std::string::npos || p2 == std::string::npos ){}
			else
			{
				std::string str = line.substr( p1+1, p2-(p1+2) );
				Ogre::StringUtil::trim( str );
				str = Ogre::StringUtil::replaceAll( str, "\"", "" );
				bool ret = mInclude( FilePath(str) );
				if( !ret )
				{
					throw PreprocessorErrors::InvalidInclude( this, str, unit_name, line_id );
				}
			}
		}
		else if( Ogre::StringUtil::startsWith( line, "#define", false ) )
		{
			std::string n, v, w;
			std::stringstream ss(line.substr( 8, line.size()-8 ));
			ss >> n;
			while( ss >> w )
			{
				v = v+w+" ";
			}
			n = Ogre::StringUtil::replaceAll( n, "\"", "" );
			v = Ogre::StringUtil::replaceAll( v, "\"", "" );
			Ogre::StringUtil::trim( n );
			Ogre::StringUtil::trim( v );
			if( n.empty() ) 
					throw PreprocessorErrors::InvalidDefine( this, "", unit_name, line_id );
				
			mDefine( n, v );
		}
		else 
			throw PreprocessorErrors::InvalidDirective( this, line, unit_name, line_id );
	}
	void ScriptPreprocessor::setDefaultIncludeCallback( ){ 
		mDefine = boost::bind( ScriptPreprocessor::_dummy_define, _1, _2 );
	}
	void ScriptPreprocessor::setDefaultDefineCallback( ){
		mInclude = boost::bind( ScriptPreprocessor::_dummy_include, _1 );
	}
	ScriptPreprocessor::Section ScriptPreprocessor::stripComments( const ScriptPreprocessor::Section & section )
	{
		std::string cont = section.join( );
		size_t cp = 0;
		size_t oldcp = 0;
		while( (cp = cont.find("/*",cp)) != std::string::npos )
		{
			oldcp = cp;
			cp = cont.find("*/",cp);
			if( cp != std::string::npos && oldcp != std::string::npos )
			{
				cp+=2;
				std::string com = cont.substr( oldcp, cp-oldcp );
				cont.erase( oldcp, cp-oldcp );
				Ogre::LogManager::getSingleton().logMessage("Stripping multiline comment '"+com+"'", Ogre::LML_NORMAL, true);
			}
		}
		Section sec( section.name, cont, section.line_offset );
		size_t id=1;
		for( auto i=sec.d.begin(); i!=sec.d.end(); i++ )
		{
			if( !i->empty() )
			{
				std::string line = (*i);
				size_t len = line.size();
				Ogre::StringUtil::trim(line);
				
				size_t p=0;
				p =line.find_first_of( "//" );
				if( p != std::string::npos )
				{
					size_t ep = line.find('\n', p);
					std::string com ;
					if( ep != std::string::npos )
					{
						com = line.substr( p+2, ep-(p+2) );
						line.erase( p, ep-p );
					}
					else{
						com = line.substr( p+2, line.size()-(p+2) ); 
						line.erase( p, line.size()-p );
					}
					Ogre::LogManager::getSingleton().logMessage("Stripping comment '"+com+"'", Ogre::LML_NORMAL, true);
				}
				Ogre::StringUtil::trim( line );
				(*i) = line;
			}
		}
		return sec;
	}
	// True to register afterwards
	void ScriptPreprocessor::processScriptSection( ScriptPreprocessor::Section & section )
	{
		section = stripComments( section );
		size_t id=1;
		for( auto i=section.d.begin(); i!=section.d.end(); i++ )
		{
			if( !i->empty() )
			{
				std::string line = (*i);
				size_t len = line.size();
				Ogre::StringUtil::trim(line);
				(*i) = Ogre::StringUtil::replaceAll( (*i), "__MODULE_LINE__", Ogre::StringConverter::toString(id)+section.line_offset );
				(*i) = Ogre::StringUtil::replaceAll( (*i), "__LINE__", Ogre::StringConverter::toString(id) );
				(*i) = Ogre::StringUtil::replaceAll( (*i), "__FILE__", "\""+section.name+"\"" );

				if( Ogre::StringUtil::startsWith( line, "#", false ) )
				{
					processDirective( section.name, line, id+section.line_offset );

					i = section.d.erase( i );
				}
				
			}
			id ++;
		}
	}
};